package com.lfy.modules.disk.service.impl;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.lfy.aop.enums.FileStatusEnum;
import com.lfy.modules.common.config.sys.DictionariesKey;
import com.lfy.modules.common.config.sys.SysDictionaries;
import com.lfy.domain.anno.Describe;
import com.lfy.encrypt.AESUtil;
import com.lfy.modules.common.custom.FileService;
import com.lfy.modules.common.custom.constants.FileConstant;
import com.lfy.modules.common.custom.constants.FileShareTypeEnum;
import com.lfy.modules.common.custom.constants.FileTypeEnum;
import com.lfy.modules.common.custom.constants.PrivateEnum;
import com.lfy.modules.common.domain.R;
import com.lfy.modules.common.domain.entity.UserEntity;
import com.lfy.modules.common.intercept.AuthHandlerInterceptor;
import com.lfy.modules.disk.domain.DiskChunkRecordEntity;
import com.lfy.modules.disk.domain.DiskFileEntity;
import com.lfy.modules.disk.domain.DiskShareDetailEntity;
import com.lfy.modules.disk.domain.dto.CreateFolderDTO;
import com.lfy.modules.disk.domain.dto.ReNameDTO;
import com.lfy.modules.disk.domain.dto.ShareDTO;
import com.lfy.modules.common.domain.FileUrlVO;
import com.lfy.modules.disk.domain.vo.FileVO;
import com.lfy.modules.disk.domain.vo.ShareFilePwdVO;
import com.lfy.modules.disk.service.DiskChunkRecordService;
import com.lfy.modules.disk.service.DiskFileService;
import com.lfy.modules.disk.service.DiskService;
import com.lfy.modules.disk.service.DiskShareDetailService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Service
@Describe("删除超过三天上传失败的分片")
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
@Slf4j
public class DiskServiceImpl implements DiskService, Job {
    @Resource
    private FileService fileService;
    @Resource
    private DiskFileService diskFileService;
    @Resource
    private ThreadPoolExecutor mainThreadPool;
    @Resource
    private DiskChunkRecordService diskChunkRecordService;
    @Resource
    private DiskShareDetailService shareDetailService;
    @Value("${ase.key}")
    private String aseKey;

    @Override
    public R<Void> uploadSmallFile(MultipartFile file, UserEntity user, Long parentId, Integer type) {
        DiskFileEntity diskFileEntity = new DiskFileEntity();
        String dir = FileConstant.builderDiskSmallFileDir(user.getId());

        diskFileEntity.setName(file.getOriginalFilename());
        diskFileEntity.setFileSize(FileConstant.convertToMB(file.getSize()));
        diskFileEntity.setIsPrivate(PrivateEnum.PUBLIC.getCode());
        diskFileEntity.setTenantId(user.getId());
        diskFileEntity.setParentId(parentId);
        diskFileEntity.setType(type);
        // 上传文件
        String path = fileService.uploadSingle(file, dir);
        diskFileEntity.setFilePath(path);
        diskFileService.save(diskFileEntity);
        return R.success();
    }

    @Override
    public void deleteChunkFile() {
        fileService.removeDir("1/article/2024-05-08");
    }

    @Override
    public R<Void> createFolder(CreateFolderDTO dto, UserEntity user) {
        DiskFileEntity diskFileEntity = new DiskFileEntity();
        diskFileEntity.setParentId(dto.getParentId());
        diskFileEntity.setName(dto.getFolderName());
        diskFileEntity.setType(FileTypeEnum.FOLDER.getCode());
        diskFileEntity.setTenantId(user.getId());
        diskFileEntity.setIsPrivate(dto.getIsPrivate());
        diskFileService.save(diskFileEntity);
        return R.success();
    }

    @Override
    public R<List<FileVO>> getFileListByParentId(Long parentId, UserEntity user) {
        List<DiskFileEntity> list = diskFileService.list(new LambdaQueryWrapper<DiskFileEntity>().eq(DiskFileEntity::getTenantId, user.getId())
                .eq(DiskFileEntity::getParentId, parentId).orderByAsc(DiskFileEntity::getType).orderByAsc(DiskFileEntity::getCreateTime));
        if (CollectionUtils.isEmpty(list)) {
            return R.success(Collections.emptyList());
        }
        String prefix = SysDictionaries.getValue(DictionariesKey.SYS_MINIO_SITE);
        System.out.println(prefix);
        List<FileVO> result = list.stream().map(item -> {
            FileVO fileVO = new FileVO();
            BeanUtils.copyProperties(item, fileVO);
            fileVO.setCreateTime(DateUtil.formatDateTime(item.getCreateTime()));
            fileVO.setTypeName(FileTypeEnum.getMsg(item.getType()));
            if (Objects.nonNull(item.getFilePath())) {
                fileVO.setFilePath(prefix + item.getFilePath());
            }
            return fileVO;
        }).collect(Collectors.toList());
        return R.success(result);
    }

    @Override
    public R<Void> rename(ReNameDTO dto, UserEntity user) {
        DiskFileEntity diskFileEntity = new DiskFileEntity();
        diskFileEntity.setName(dto.getName());
        diskFileService.update(diskFileEntity, new LambdaQueryWrapper<DiskFileEntity>().eq(DiskFileEntity::getId, dto.getId()).eq(DiskFileEntity::getTenantId, user.getId()));
        return R.success();
    }

    @Override
    public R<Void> deleteFile(Long id, UserEntity user) {
        DiskFileEntity diskFileEntity = diskFileService.getOne(new LambdaQueryWrapper<DiskFileEntity>().eq(DiskFileEntity::getId, id).eq(DiskFileEntity::getTenantId, user.getId()));
        if (diskFileEntity == null) {
            return R.error("文件不存在");
        }
        //找到所有子文件
        if (FileTypeEnum.FOLDER.getCode().equals(diskFileEntity.getType())) {
            Long parentId = diskFileEntity.getId();
            List<DiskFileEntity> list = findChildFile(parentId, user.getId());
            if (!CollectionUtils.isEmpty(list)) {
                //删除所有表中文件记录
                Set<Long> ids = list.stream().map(DiskFileEntity::getId).collect(Collectors.toSet());
                ids.add(id);
                //删除所有网盘文件
                List<String> paths = list.stream().filter(item -> !item.getType().equals(FileTypeEnum.FOLDER.getCode())).map(DiskFileEntity::getFilePath).collect(Collectors.toList());
                mainThreadPool.execute(() -> fileService.removeFileByNames(paths));
                diskFileService.removeByIds(ids);
            } else {
                //删除空文件夹
                diskFileService.remove(new LambdaQueryWrapper<DiskFileEntity>().eq(DiskFileEntity::getId, id).eq(DiskFileEntity::getTenantId, user.getId()));
            }
            return R.success();
        }
        //直接删除文件
        mainThreadPool.execute(() -> fileService.removeFileByNames(Arrays.asList(diskFileEntity.getFilePath())));
        diskFileService.removeById(id);
        return R.success();
    }

    @Override
    public R<Boolean> exist(String md5, String fileName, Long parentId) {
        DiskFileEntity fileEntity = this.diskFileService.getOne(new LambdaQueryWrapper<DiskFileEntity>().eq(DiskFileEntity::getFileMd5, md5));
        if (Objects.isNull(fileEntity)) {
            //文件不存在，无法秒传
            DiskChunkRecordEntity diskChunkRecordEntity = new DiskChunkRecordEntity();
            diskChunkRecordEntity.setName(fileName);
            diskChunkRecordEntity.setMd5(md5);
            diskChunkRecordEntity.setStatus(FileStatusEnum.UPLOADING.getCode());
            UserEntity user = AuthHandlerInterceptor.authLocal.get();
            String dir = FileConstant.builderDiskBigFileCutDir(user.getId(), md5);
            diskChunkRecordEntity.setCatalogue(dir);
            this.diskChunkRecordService.save(diskChunkRecordEntity);
            return R.success(false);
        }
        String pref = fileName.split("\\.")[0];
        String type = FileConstant.getFileType(fileName);
        fileName = pref + "_" + System.currentTimeMillis() + type;
        //文件存在 拷贝到当前用户下
        UserEntity user = AuthHandlerInterceptor.authLocal.get();
        fileService.copyFile(fileEntity.getFilePath(), FileConstant.builderDiskBigFileDir(user.getId(), md5).concat(fileName));
        DiskFileEntity diskFileEntity = new DiskFileEntity();
        diskFileEntity.setParentId(parentId);
        diskFileEntity.setName(fileName);
        diskFileEntity.setTenantId(user.getId());
        diskFileEntity.setFilePath(FileConstant.builderDiskBigFileDir(user.getId(), md5).concat(fileName));
        diskFileEntity.setFileSize(fileEntity.getFileSize());
        diskFileEntity.setFileMd5(md5);
        diskFileEntity.setType(fileEntity.getType());
        diskFileEntity.setIsPrivate(PrivateEnum.PUBLIC.getCode());
        this.diskFileService.save(diskFileEntity);
        return R.success(true);

    }

    /**
     * 校验分片文件是否已经上传过
     */
    @Override
    public R<Boolean> checkChunk(String md5, Integer chunk) {

        UserEntity userEntity = AuthHandlerInterceptor.authLocal.get();
        String dir = FileConstant.builderDiskBigFileCutDir(userEntity.getId(), md5);
        boolean b = this.fileService.checkChunk(dir.concat(String.valueOf(chunk)));
        return R.success(b);
    }

    @Override
    public R<Void> uploadChunk(MultipartFile file, String md5, Integer chunk, UserEntity user) {
        String dir = FileConstant.builderDiskBigFileCutDir(user.getId(), md5);
        fileService.uploadChunk(file, dir.concat(String.valueOf(chunk)));
        return R.success();
    }

    @Override
    public R<Void> merge(String md5, String fileName, Long parentId, UserEntity user, Integer cutSize, Integer type, Double fileSize) {
        DiskFileEntity diskFileEntity = new DiskFileEntity();
        diskFileEntity.setName(fileName);


        String pref = fileName.split("\\.")[0];
        String fileType = FileConstant.getFileType(fileName);
        fileName = pref + "_" + System.currentTimeMillis() + fileType;

        String dir = FileConstant.builderDiskBigFileCutDir(user.getId(), md5);
        String mergePath = FileConstant.builderDiskBigFileDir(user.getId(), md5).concat(fileName);
        boolean merge = fileService.merge(dir, fileName, cutSize, mergePath);
        if (!merge) {
            return R.error("合并文件失败");
        }
        diskFileEntity.setFileMd5(md5);
        diskFileEntity.setType(type);
        diskFileEntity.setParentId(parentId);
        ;
        diskFileEntity.setTenantId(user.getId());
        diskFileEntity.setFilePath(mergePath);
        diskFileEntity.setFileSize(fileSize);
        diskFileEntity.setIsPrivate(PrivateEnum.PUBLIC.getCode());
        this.diskFileService.save(diskFileEntity);
        mainThreadPool.execute(() -> {
            fileService.removeDir(dir);
            //更新分片记录状态
            DiskChunkRecordEntity diskChunkRecordEntity = new DiskChunkRecordEntity();
            diskChunkRecordEntity.setStatus(FileStatusEnum.SUCCESS.getCode());
            this.diskChunkRecordService.update(diskChunkRecordEntity, new LambdaQueryWrapper<DiskChunkRecordEntity>().eq(DiskChunkRecordEntity::getMd5, md5));
        });
        return R.success();


    }

    @Override
    public ResponseEntity<byte[]> download(Long id, UserEntity user, HttpServletResponse response) {
        DiskFileEntity one = this.diskFileService.getOne(new LambdaQueryWrapper<DiskFileEntity>().eq(DiskFileEntity::getId, id).eq(DiskFileEntity::getTenantId, user.getId()));
        if (one == null) {
            throw new IllegalArgumentException("文件不存在");
        }

        String path = one.getFilePath();
        return fileService.download(one.getName(), path);

    }

    @Override
    public void downloadToStream(Long id, UserEntity user, HttpServletResponse response) {
//        DiskFileEntity one = this.diskFileService.getOne(new LambdaQueryWrapper<DiskFileEntity>().eq(DiskFileEntity::getId, id).eq(DiskFileEntity::getTenantId, user.getId()));
        DiskFileEntity one = this.diskFileService.getOne(new LambdaQueryWrapper<DiskFileEntity>().eq(DiskFileEntity::getId, id));
        if (one == null) {
            throw new IllegalArgumentException("文件不存在");
        }
        String path = one.getFilePath();

         fileService.downloadToStream(one.getName(), path,one.getType(), response);

    }

    @Override
    public R<Void> paste(Long id, Long parentId, UserEntity user) {
        DiskFileEntity entity = this.diskFileService.getOne(new LambdaQueryWrapper<DiskFileEntity>().eq(DiskFileEntity::getId, id).eq(DiskFileEntity::getTenantId, user.getId()));
        if (entity == null) {
            return R.error("文件不存在");
        }
        entity.setParentId(parentId);
        this.diskFileService.updateById(entity);
        return R.success();
    }

    /**
     * 创建分享链接
     *
     * @param dto  分享信息
     * @param user 用户
     * @return 分享链接
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<ShareFilePwdVO> createShareFile(ShareDTO dto, UserEntity user) {
        if (dto.getShareType().equals(FileShareTypeEnum.MANUAL.getCode())
                && (StringUtils.isEmpty(dto.getShareCode()))) {
            return R.error("秘钥格式不对");
        }
        DiskShareDetailEntity diskShareDetailEntity = new DiskShareDetailEntity();

        if (FileShareTypeEnum.AUTO.getCode().equals(dto.getShareType())) {
            String code = generateRandomFourCharacterString();
            diskShareDetailEntity.setShareCode(code);
        } else {

            diskShareDetailEntity.setShareCode(dto.getShareCode());
        }
        DiskFileEntity fileDetail = this.diskFileService.getById(dto.getFileId());

        if (fileDetail == null) {
            return R.error("文件不存在");
        }
        if (!fileDetail.getTenantId().equals(user.getId())) {
            return R.error("文件不属于当前用户");
        }
        diskShareDetailEntity.setFileId(dto.getFileId());
        diskShareDetailEntity.setShareLimitType(dto.getShareLimitType());
        diskShareDetailEntity.setShareLimitNum(dto.getShareLimitNum());
        diskShareDetailEntity.setTenantId(user.getId());
        diskShareDetailEntity.setShareLimitSurplus(dto.getShareLimitNum());
        diskShareDetailEntity.setTimeStampSave(System.currentTimeMillis());
        diskShareDetailEntity.setShareTimeType(dto.getShareTimeType());

        //生成分享链接
        ShareFilePwdVO shareFilePwdVO = new ShareFilePwdVO();
        String webSite = SysDictionaries.getValue(DictionariesKey.SYS_WEB_SITE);
        String route = SysDictionaries.getValue(DictionariesKey.SYS_SHARE_FILE_ROUTE);
        this.shareDetailService.save(diskShareDetailEntity);
        try {
            String param = AESUtil.encrptString(diskShareDetailEntity.getId().toString(), aseKey);
            String base64 = Base64.getUrlEncoder().withoutPadding()
                    .encodeToString(param.getBytes(StandardCharsets.UTF_8));


            String shareLink = webSite.concat(route).concat("/").concat(base64);
            if (Objects.nonNull(dto.getAutoPwd()) && dto.getAutoPwd()) {
                shareLink = shareLink.concat("?code=").concat(diskShareDetailEntity.getShareCode());
            }
            diskShareDetailEntity.setShareUrl(shareLink);
            diskShareDetailEntity.setShareUri(shareLink.replace(webSite, ""));
            this.shareDetailService.updateById(diskShareDetailEntity);
            shareFilePwdVO.setShareLink(shareLink);
            shareFilePwdVO.setSharePwd(diskShareDetailEntity.getShareCode());
        } catch (Exception e) {
            log.error("in DiskServiceImpl.createShareFile 加密失败", e);
        }
        return R.success(shareFilePwdVO);
    }

    public static String generateRandomFourCharacterString() {
        StringBuilder sb = new StringBuilder();
        SecureRandom random = new SecureRandom();
        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

        for (int i = 0; i < 4; i++) {
            sb.append(chars.charAt(random.nextInt(chars.length())));
        }

        return sb.toString();
    }

    /**
     * 找到所有子文件
     *
     * @param parentId 父目录id
     * @param id       用户id
     */
    private List<DiskFileEntity> findChildFile(Long parentId, Long id) {
        ArrayList<DiskFileEntity> result = new ArrayList<>();
        List<DiskFileEntity> list = this.diskFileService.list(new LambdaQueryWrapper<DiskFileEntity>().eq(DiskFileEntity::getTenantId, id).eq(DiskFileEntity::getParentId, parentId));
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }

        for (DiskFileEntity diskFileEntity : list) {
            if (FileTypeEnum.FOLDER.getCode().equals(diskFileEntity.getType())) {
                List<DiskFileEntity> childFile = findChildFile(diskFileEntity.getId(), id);
                if (!CollectionUtils.isEmpty(childFile)) {
                    result.addAll(childFile);
                }
            }
            result.add(diskFileEntity);
        }
        return result;
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        List<DiskChunkRecordEntity> list = this.diskChunkRecordService.list();
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        for (DiskChunkRecordEntity diskChunkRecordEntity : list) {
            if (diskChunkRecordEntity.getStatus().equals(FileStatusEnum.UPLOADING.getCode())
                    && diskChunkRecordEntity.getCreateTime().getTime() + 1000 * 60 * 60 * 24 * 3 < System.currentTimeMillis()) {
                //超时未上传，删除记录
                try {
                    this.diskChunkRecordService.removeById(diskChunkRecordEntity.getId());
                    this.fileService.removeDir(diskChunkRecordEntity.getCatalogue());
                } catch (Exception e) {
                    log.error("删除分片记录失败，文件名:{},目录:{},md5:{}", diskChunkRecordEntity.getName(), diskChunkRecordEntity.getCatalogue(), diskChunkRecordEntity.getMd5());
                }
            }
        }
    }
}
